﻿using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing.Design;
using System.Reflection;

namespace Qreed.Windows.ComponentModel
{
    /// <summary>
    /// The <c>PropertyBagItem</c> is the property you will see in the <see cref="PropertyGrid"/>
    /// </summary>
    public class PropertyBagItem
    {
        #region Construction

        /// <summary>
        /// Initializes a new instance of the <see cref="PropertyBagItem"/> class.
        /// </summary>
        /// <param name="displayName">The display name.</param>
        /// <param name="value">The value.</param>
        /// <param name="type">The type.</param>
        public PropertyBagItem(string displayName, object value, Type type) 
            : this(displayName, displayName, value, value, null, null, false, true, type) 
        { }

        /// <summary>
        /// Initializes a new instance of the <see cref="PropertyBagItem"/> class.
        /// </summary>
        /// <param name="displayName">The display name.</param>
        /// <param name="value">The value.</param>
        /// <param name="category">The category.</param>
        /// <param name="description">The description.</param>
        /// <param name="isReadOnly">if set to <c>true</c> the property is read only.</param>
        /// <param name="isBrowsable">if set to <c>true</c> the property is browsable.</param>
        /// <param name="type">The type.</param>
        public PropertyBagItem(string displayName, object value,
                               string category, string description,
                               bool isReadOnly, bool isBrowsable, Type type)
            : this(displayName, displayName, value, value, category, description, isReadOnly, isBrowsable, type)
        {}

        /// <summary>
        /// Initializes a new instance of the <see cref="PropertyBagItem"/> class.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="displayName">The display name.</param>
        /// <param name="value">The value.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <param name="category">The category.</param>
        /// <param name="description">The description.</param>
        /// <param name="isReadOnly">if set to <c>true</c> the property is read only.</param>
        /// <param name="isBrowsable">if set to <c>true</c> the property is browsable.</param>
        /// <param name="type">The type.</param>
        public PropertyBagItem(string name, string displayName, 
                               object value, object defaultValue, 
                               string category, string description, 
                               bool isReadOnly, bool isBrowsable, Type type)
        {
            _name = name;
            _displayName = displayName;
            _value = value;
            _defaultValue = defaultValue;
            _category = category;
            _description = description;
            _isReadOnly = isReadOnly;
            _isBrowsable = isBrowsable;
            _type = type;
        }

        #endregion

        #region Properties

        private string _name;
        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        /// <value>The name.</value>
        public string Name 
        {
            get { return _name; }
            set { _name = value; }
        }

        /*private string _key;
        /// <summary>
        /// Gets or sets the key.
        /// </summary>
        /// <value>The key.</value>
        public string Key
        {
            get 
            {
                if (this._key == null)
                    return Name;

                return this._key; 
            }
            set { this._key = value; }
        }	*/		

        private PropertyBag _bag;
        /// <summary>
        /// Gets or sets the property bag.
        /// </summary>
        /// <value>The property bag.</value>
        public virtual PropertyBag Bag 
        {
            get { return _bag; }
            internal set { _bag = value; } 
        }

        private string _description;
        /// <summary>
        /// Gets or sets the description.
        /// </summary>
        /// <value>The description.</value>
        public string Description
        {
            get { return _description; }
            set { _description = value; }
        }

        private string _category;
        /// <summary>
        /// Gets or sets the category.
        /// </summary>
        /// <value>The category.</value>
        public string Category
        {
            get { return _category; }
            set { _category = value; }
        }

        private string _displayName;
        /// <summary>
        /// Gets or sets my property.
        /// </summary>
        /// <value>My property.</value>
        public string DisplayName 
        {
            get { return _displayName; }
            set { _displayName = value; }
        }

        private bool _isReadOnly ;
        /// <summary>
        /// Gets or sets a value indicating whether this instance is read only.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is read only; otherwise, <c>false</c>.
        /// </value>
        public bool IsReadOnly 
        {
            get { return _isReadOnly ; }
            set { _isReadOnly  = value; }
        }

        private object _defaultValue;
        /// <summary>
        /// Gets or sets the default value.
        /// </summary>
        /// <value>The default value.</value>
        public object DefaultValue
        {
            get { return _defaultValue; }
            set { _defaultValue = value; }
        }

        private Type _type;
        /// <summary>
        /// Gets or sets the type.
        /// </summary>
        /// <value>The type.</value>
        public Type Type
        {
            get { return _type; }
            set { _type = value; }
        }

        private bool _isBrowsable = true;
        /// <summary>
        /// Gets or sets a value indicating whether this instance is browsable.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is browsable; otherwise, <c>false</c>.
        /// </value>
        public bool IsBrowsable
        { 
            get { return _isBrowsable; }
            set { _isBrowsable = value; }
        }

        private object _value;
        /// <summary>
        /// Gets or sets the value.
        /// </summary>
        /// <value>The value.</value>
        public object Value 
        {
            get
            {
                return _value;
            }
            set
            {
                _value = value;
            }
        }

        private object _tag;
        /// <summary>
        /// Gets or sets the tag.
        /// </summary>
        /// <value>The tag.</value>
        public object Tag
        {
            get { return _tag; }
            set 
            { 
                _tag = value;
                //Debug.Assert(this.GetType() == typeof(PropertyBagItem) && _tag.GetType() != typeof(object[]));
            }
        }

        private TypeConverter _typeConverter = null;
        /// <summary>
        /// Gets or sets the type converter.
        /// </summary>
        /// <value>The type converter.</value>
        public TypeConverter TypeConverter
        {
            get { return this._typeConverter; }
            set { this._typeConverter = value; }
        }

        private UITypeEditor _typeEditor;
        /// <summary>
        /// Gets or sets the type editor.
        /// </summary>
        /// <value>The type editor.</value>
        public UITypeEditor TypeEditor
        {
            get { return _typeEditor; }
            set { _typeEditor = value; }
        }

        #endregion

        internal static PropertyBagItem FromPropertyInfo(PropertyInfo pi, object target)
        {
            DisplayNameAttribute displayNameAttribute = (DisplayNameAttribute)GetCustomAttribute(pi, typeof(DisplayNameAttribute));
            BrowsableAttribute browsableAttribute = (BrowsableAttribute)GetCustomAttribute(pi, typeof(BrowsableAttribute));
            ReadOnlyAttribute readOnlyAttribute = (ReadOnlyAttribute)GetCustomAttribute(pi, typeof(ReadOnlyAttribute));
            DescriptionAttribute descriptionAttribute = (DescriptionAttribute)GetCustomAttribute(pi, typeof(DescriptionAttribute));
            CategoryAttribute categoryAttribute = (CategoryAttribute)GetCustomAttribute(pi, typeof(CategoryAttribute));
            DefaultValueAttribute defaultValueAttribute = (DefaultValueAttribute)GetCustomAttribute(pi, typeof(DefaultValueAttribute));
            TypeConverterAttribute typeConverterAttribute = (TypeConverterAttribute)GetCustomAttribute(pi, typeof(TypeConverterAttribute));

            object value = pi.GetValue(target, null);
            
            PropertyBagItem item = new PropertyBagItem(pi.Name,
                                                       displayNameAttribute == null ? pi.Name : displayNameAttribute.DisplayName,
                                                       value,
                                                       defaultValueAttribute == null ? null : defaultValueAttribute.Value,
                                                       categoryAttribute == null ? null : categoryAttribute.Category,
                                                       descriptionAttribute == null ? null : descriptionAttribute.Description,
                                                       readOnlyAttribute == null ? false : readOnlyAttribute.IsReadOnly,
                                                       browsableAttribute == null ? true : browsableAttribute.Browsable,
                                                       pi.PropertyType);

            if (typeConverterAttribute != null)
            {
                Type typeConverterType = Type.GetType(typeConverterAttribute.ConverterTypeName, true, false);
                item.TypeConverter = (TypeConverter)Activator.CreateInstance(typeConverterType);
            }
    
            return item;
        }

        private static object GetCustomAttribute(PropertyInfo pi, Type attributeType)
        {
            object[] attributes = pi.GetCustomAttributes(attributeType, true);

            if (attributes != null && attributes.Length > 0)
                return attributes[0];

            return null;
        }

        /// <summary>
        /// Gets the attributes.
        /// </summary>
        /// <returns></returns>
        internal List<Attribute> GetItemAttributes()
        {
            List<Attribute> ret = new List<Attribute>();

            if (!string.IsNullOrEmpty(DisplayName))
                ret.Add(new DisplayNameAttribute(DisplayName));

            ret.Add(new NotifyParentPropertyAttribute(true));
            ret.Add(new BrowsableAttribute(IsBrowsable));
            ret.Add(new ReadOnlyAttribute(IsReadOnly));

            if (!string.IsNullOrEmpty(Description))
                ret.Add(new DescriptionAttribute(Description));

            if (!string.IsNullOrEmpty(Category))
                ret.Add(new CategoryAttribute(Category));
            
            return ret;
        }

        /// <summary>
        /// Sets the value.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <remarks>To access the protected SetValue.
        /// </remarks>
        internal void SetValueInternal(object value)
        {
            SetValue(value);
        }

        /// <summary>
        /// Gets the value.
        /// </summary>
        /// <returns>The value of the property.</returns>
        /// <remarks>
        /// To access protected GetValue
        /// </remarks>
        internal object GetValueInternal()
        {
            return GetValue();
        }

        /// <summary>
        /// Sets the value.
        /// </summary>
        /// <param name="value">The value.</param>
        protected virtual void SetValue(object value)
        {
            if (Bag != null)
            {
                PropertyBagEventArgs e = new PropertyBagEventArgs(this, value);
                Bag.OnSetValue(e);
                value = e.Value;
            }

            Value = value;
        }

        /// <summary>
        /// Gets the value.
        /// </summary>
        /// <returns>The value of the property</returns>
        protected virtual object GetValue()
        {
            PropertyBagEventArgs e = new PropertyBagEventArgs(this, Value);

            if (Bag != null)
                Bag.OnGetValue(e);

            Value = e.Value;

            return Value;
        }

        /// <summary>
        /// Gets the property descriptor for this item.
        /// </summary>
        /// <param name="attributes">The attributes.</param>
        /// <returns>A property descriptor for this property bag item.</returns>
        public virtual PropertyDescriptor GetPropertyDescriptor(Attribute[] attributes)
        {
            return new PropertyBagItemDescriptor(this, attributes);
        }
    }
}
